/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package it.polimi.deib.se2.meteocal.gui;

import it.polimi.deib.se2.meteocal.business.boundary.CalendarManager;
import it.polimi.deib.se2.meteocal.business.boundary.EventManager;
import it.polimi.deib.se2.meteocal.business.boundary.InvitationManager;
import it.polimi.deib.se2.meteocal.business.boundary.LocationManager;
import it.polimi.deib.se2.meteocal.business.boundary.NotificationManager;
import it.polimi.deib.se2.meteocal.business.boundary.UserManager;
import it.polimi.deib.se2.meteocal.business.control.Utils;
import it.polimi.deib.se2.meteocal.business.control.WeatherCodeConverter;
import it.polimi.deib.se2.meteocal.business.entity.Calendar;
import it.polimi.deib.se2.meteocal.business.entity.Event;
import it.polimi.deib.se2.meteocal.business.entity.Invitation;
import it.polimi.deib.se2.meteocal.business.entity.Location;
import it.polimi.deib.se2.meteocal.business.entity.Notification;
import it.polimi.deib.se2.meteocal.business.entity.User;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import org.json.JSONException;
import org.primefaces.event.SelectEvent;
import org.primefaces.model.DefaultScheduleModel;
import org.primefaces.model.ScheduleEvent;
import org.primefaces.model.ScheduleModel;

@Named
@SessionScoped
public class CalendarBean implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @EJB
    private EventManager em;
    @EJB
    private CalendarManager cl;
    @EJB
    private LocationManager lm;
    @EJB
    private UserManager um;
    @EJB
    private InvitationManager im;
    @EJB
    private NotificationManager nm;
    
    @Inject
    private UserBean ub;
    @Inject
    private LocationBean lb;
    @Inject
    private NotificationBean nb;
    
    private Event event;
    private int badWeatherLevel;
    private Date startDate;
    private Date endDate;
    private boolean isPublic;
    private boolean outdoor;
    private String title;
    private String locationName;
    private Location location;
    private long id;
    private String invitees;
    private String weather;
    private String iconURL;
    
    private ScheduleModel eventModel;
    private MeteoCalScheduleEvent scheduleEvent = new MeteoCalScheduleEvent();
    
    private boolean isNewEvent = true;
    private boolean eventOfCurrentUser =true;
    private String message;
    private boolean createEventOk=true;
    
    
    public CalendarBean() {
    }
    
    @PostConstruct
    public void init() {
        eventModel = new DefaultScheduleModel();
        List<Event> eventCollection = (List<Event>) ub.getUser().getCalendar().getEventCollection();
        
        for (Event i : eventCollection) {
            eventModel.addEvent(new MeteoCalScheduleEvent(i.getId(), i.getTitle(), i.getStartDate(), i.getEndDate()));
        }
    }
    
    public int getBadWeatherLevel() {
        return this.badWeatherLevel;
    }
    
    public void setBadWeatherLevel(int badWeatherLevel) {
        this.badWeatherLevel = badWeatherLevel;
    }
    
    public UserBean getUb() {
        return ub;
    }
    
    public void setUb(UserBean ub) {
        this.ub = ub;
    }
    
    public NotificationBean getNb() {
        return nb;
    }
    
    public void setNb(NotificationBean nb) {
        this.nb = nb;
    }
    
    public LocationBean getLb() {
        return lb;
    }
    
    public void setLb(LocationBean lb) {
        this.lb = lb;
    }
    
    public Event getEvent() {
        return event;
    }
    
    public void setEvent(Event event) {
        this.event = event;
    }
    
    public String getInvitees(){
        return invitees;
    }
    
    public void setInvitees(String invitees){
        this.invitees = invitees;
    }
    
    public String getWeather(){
        return weather;
    }
    
    public void setWeather(String weather){
        this.weather = weather;
    }
    
    public String getIconURL(){
        return iconURL;
    }
    
    public void setIconURL(String iconURL){
        this.iconURL = iconURL;
    }
    
    
    public EventManager getEm() {
        return em;
    }
    
    public void setEm(EventManager em) {
        this.em = em;
    }
    
    public NotificationManager getNm() {
        return nm;
    }
    
    public void setNm(NotificationManager nm) {
        this.nm = nm;
    }
    
    public InvitationManager getIm() {
        return im;
    }
    
    public void setIm(InvitationManager im) {
        this.im = im;
    }
    
    public UserManager getUm() {
        return um;
    }
    
    public void setUm(UserManager um) {
        this.um = um;
    }
    
    //questo metodo e' attualmente chiamato in caso di modifica di un evento ma anche di creazione, aggiustare nome o funzioni
    public void createEvent(){
        setupEvent();
        
        //insert calendar reference to event
        getEvent().setCalendar(ub.getUser().getCalendar());
        //save event to database
        if(isIsNewEvent()) {
            getEm().save(getEvent());
            Calendar cal = ub.getUser().getCalendar();
            Collection<Event> eventCollection = ub.getUser().getCalendar().getEventCollection();
            eventCollection.add(event);
            cal.setEventCollection(eventCollection);
            ub.getUser().setCalendar(cal);
            cl.editCalendar(cal);
        } else {
            getEm().edit(getEvent());
            ScheduleEvent eventToRemove = eventModel.getEvent(scheduleEvent.getId());
            eventModel.deleteEvent(eventToRemove);
        }
        
        //update calendar with the new event
        
        eventModel.addEvent(new MeteoCalScheduleEvent(getEvent().getId(), getEvent().getTitle(), getEvent().getStartDate(), getEvent().getEndDate()));
        
        if(invitees != null){
            String[] invitee = invitees.split(",( )*");
            for(String i : invitee){
                boolean cond = false;
                for (Iterator<Event> it = um.find(i).getCalendar().getEventCollection().iterator(); it.hasNext();) {
                    Event ev = it.next();
                    if( Objects.equals(ev.getId(), getEvent().getId()))
                        cond = true;
                }
                
                //if the event already exists in the invitee's calendar I send a notification, otherwise I send the invitation
                if(cond){
                    String details = "Event rescheduled - The event " + getEvent().getTitle() + "has been rescheduled";
                    User u = um.find(i);
                    getNm().sendNotification(u, getEvent(), null, details);
                }else
                    sendInvitation(i);
            }
        }
        if(getLocation() != null){
            String[] splittedWeather = new String[16];
            if(getLocation().getWeather() != null){
                splittedWeather = getLocation().getWeather().split(",",-1);
            }
            
            int days = (int) Utils.getDateDiff(startDate, TimeUnit.DAYS);
            
            boolean condition;
            try {
                condition = (getLocation().getWeather() == null) || ("null".equals(splittedWeather[days])) || ("Meteo not found".equals(splittedWeather[days]));
            }catch(ArrayIndexOutOfBoundsException e){
                condition = false;
            }
            if(condition){
                try {
                    lb.attachWeatherToSingleLocation(location);
                } catch (JSONException | IOException ex) {
                    Logger.getLogger(CalendarBean.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        
    }
    
    private void setupEvent() {
        Event e = new Event();
        if(isIsNewEvent()) {
            e.setId(System.currentTimeMillis());
        } else {
            e.setId(getId());
        }
        e.setBadWeatherLevel(badWeatherLevel);
        e.setStartDate(getStartDate());
        e.setEndDate(getEndDate());
        e.setTitle(title);
        e.setIsPublic(isPublic);
        e.setOutdoor(outdoor);
        e.setCreator(ub.getUser());
        setEvent(e);
        if(getLocationName()!= null){
            Long id = getLm().isIn(getLocationName());
            this.location = new Location();
            getLocation().setLocName(getLocationName());
            if(id == 0) {
                getLocation().setId(System.currentTimeMillis());
                getLm().save(getLocation());
            } else {
                getLocation().setId(id);
                getLocation().setWeather((getLm().find(id)).getWeather());
            }
            
            setLocation(location);
            e.setIdLocation(getLocation());
            getLocation().getEvent().add(e);
        }
        
    }
    
    //campo data precompilato quando schiaccio su un quadratino
    public void onDateSelect(SelectEvent selectEvent) {
        cleanField();
        setStartDate((Date) selectEvent.getObject());
        setEndDate((Date) selectEvent.getObject());
        setIsNewEvent(true);
    }
    
    public void onEventSelect(SelectEvent selectEvent) {
        scheduleEvent = (MeteoCalScheduleEvent) selectEvent.getObject();
        
        //store event id to a local variable and set the operation as an edit
        setId(scheduleEvent.getIdEvent());
        setIsNewEvent(false);
        
        //precompiled field
        setTitle(scheduleEvent.getTitle());
        setStartDate(scheduleEvent.getStartDate());
        setEndDate(scheduleEvent.getEndDate());
        
        event = em.find(scheduleEvent.getIdEvent());
        
        //only the user is autorized to modify event
        if(um.getLoggedUser().getEmail().equals(event.getCreator().getEmail())){
            setEventOfCurrentUser(true);
        }else
            setEventOfCurrentUser(false);
        
        setOutdoor(event.isOutdoor());
        setIsPublic(event.getIsPublic());
        setBadWeatherLevel(event.getBadWeatherLevel());
        
        Location loc = event.getIdLocation();
        
        if(loc!=null) {
            setLocation(loc);
            setLocationName(loc.getLocName());
            String weath = Utils.catchWeather(event);
            setWeather(weath);
            String icon = WeatherCodeConverter.getIcon(weath);
            setIconURL(icon);
        }else{
            setWeather("Set a location first");
            setIconURL(WeatherCodeConverter.getIcon("0"));
        }
        
        List<Invitation> invitationList = im.getInvitations(event);
        
        //in invitation c'è event
        
        StringBuilder inv = new StringBuilder();
        
        for(Invitation i : invitationList){
            inv.append(i.getUserInvited().getEmail());
            if(i != invitationList.get(invitationList.size()-1)){
                inv.append(",");
            }
        }
        
        setInvitees(inv.toString());
        
    }
    
    /**
     * @return the startDate
     */
    public Date getStartDate() {
        return startDate;
    }
    
    /**
     * @param startDate the startDate to set
     */
    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }
    
    /**
     * @return the endDate
     */
    public Date getEndDate() {
        return endDate;
    }
    
    /**
     * @param endDate the endDate to set
     */
    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }
    
    /**
     * @return the title
     */
    public String getTitle() {
        return title;
    }
    
    /**
     * @param title the title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }
    
    /**
     * @return the isPublic
     */
    public boolean isIsPublic() {
        return isPublic;
    }
    
    /**
     * @param isPublic the isPublic to set
     */
    public void setIsPublic(boolean isPublic) {
        this.isPublic = isPublic;
    }
    
    /**
     * @return the outdoor
     */
    public boolean isOutdoor() {
        return outdoor;
    }
    
    /**
     * @param outdoor the outdoor to set
     */
    public void setOutdoor(boolean outdoor) {
        this.outdoor = outdoor;
    }
    
    /**
     * @return the eventModel
     */
    public ScheduleModel getEventModel() {
        return eventModel;
    }
    
    /**
     * @param eventModel the eventModel to set
     */
    public void setEventModel(ScheduleModel eventModel) {
        this.eventModel = eventModel;
    }
    
    /**
     * @return the scheduleEvent
     */
    public ScheduleEvent getScheduleEvent() {
        return scheduleEvent;
    }
    
    /**
     * @param scheduleEvent the scheduleEvent to set
     */
    public void setScheduleEvent(MeteoCalScheduleEvent scheduleEvent) {
        this.scheduleEvent = scheduleEvent;
    }
    
    /**
     * @return the id
     */
    public long getId() {
        return id;
    }
    
    /**
     * @param id the id to set
     */
    public void setId(long id) {
        this.id = id;
    }
    
    
    /**
     * @return the location name
     */
    public String getLocationName() {
        return locationName;
    }
    
    public void setLocationName(String locationName) {
        this.locationName = locationName;
    }
    
    public Location getLocation(){
        return location;
    }
    public void setLocation(Location location){
        this.location= location;
    }
    
    //autocomplete of location field
    public List<String> completeLocation(String query) {
        return Utils.autocomplete(getLm(), query);
    }
    
    public LocationManager getLm() {
        return lm;
    }
    
    public void setLm(LocationManager lm) {
        this.lm = lm;
    }
    public boolean isIsNewEvent() {
        return isNewEvent;
    }
    
    public void setIsNewEvent(boolean b) {
        this.isNewEvent = b;
    }
    
    private void cleanField(){
        setEventOfCurrentUser(true);
        setTitle("");
        setOutdoor(false);
        setIsPublic(false);
        setLocation(null);
        setLocationName("");
        setBadWeatherLevel(0);
        setInvitees("");
        setWeather("Set a location first");
        setIconURL(WeatherCodeConverter.getIcon("0"));
    }
    
    private void sendInvitation(String invitee) {
        
        User userInvited = getUm().find(invitee);
        
        //if invitee has already a pending invitation referring to that event,
        //delete the other invitation and the relatively notification
        boolean pendingInvitation = false;
        pendingInvitation = im.getPendingInvitationsByEventAndUser(event, userInvited);
        
        if(pendingInvitation){
            Invitation invitationToRemove = im.getInvitationByEventAndUser(event, userInvited);
            Notification notificationToRemove = invitationToRemove.getNotification_id();
            //togliere notification and invitation da event
            event.getNotifications().remove(notificationToRemove);
            event.getInvitations().remove(invitationToRemove);
            //togliere notification and invitation da user
            userInvited.getNotifications().remove(notificationToRemove);
            userInvited.getInvitations().remove(invitationToRemove);
            
            im.remove(invitationToRemove);
            nm.remove(notificationToRemove);
        }
        
        Invitation invitation = new Invitation();
        invitation.setId(System.currentTimeMillis());
        invitation.setStatus(false);
        invitation.setUserInvited(userInvited);
        invitation.setReferingEvent(event);
        
        getIm().save(invitation);
        
        userInvited.getInvitations().add(invitation);
        event.getInvitations().add(invitation);
        
        
        String details = "Event invitation - You have been invited to the event " + event.getTitle() + " by the user " + event.getCreator().getName();
        Notification notif = getNm().sendNotification(userInvited, event, invitation, details);
        invitation.setNotification_id(notif);
        im.update(invitation);
    }
    
    public void addOtherUserEvent(Event eventToAdd){
        eventToAdd.setCalendar(ub.getUser().getCalendar());
        Calendar cal = ub.getUser().getCalendar();
        Collection<Event> eventCollection = ub.getUser().getCalendar().getEventCollection();
        eventCollection.add(eventToAdd);
        cal.setEventCollection(eventCollection);
        ub.getUser().setCalendar(cal);
        cl.editCalendar(cal);
        
        
        eventModel.addEvent(new MeteoCalScheduleEvent(eventToAdd.getId(), eventToAdd.getTitle(), eventToAdd.getStartDate(), eventToAdd.getEndDate()));
    }
    
    protected void clearCalendar() {
        Calendar cal = ub.getUser().getCalendar();
        
        for(Event e : cal.getEventCollection()) {
            for(Invitation i : im.getInvitations(e)) {
                i.getUserInvited().getCalendar().getEventCollection().remove(e);
                im.remove(i);
            }
            for(Notification n : nm.getNotificationsByEvent(e)) {
                nm.remove(n);
            }
            em.remove(e);
        }
        
        cal.getEventCollection().clear();
        eventModel.clear();
    }
    
    /**
     * @return the eventOfCurrentUser
     */
    public boolean isEventOfCurrentUser() {
        return eventOfCurrentUser;
    }
    
    /**
     * @param eventOfCurrentUser the eventOfCurrentUser to set
     */
    public void setEventOfCurrentUser(boolean eventOfCurrentUser) {
        this.eventOfCurrentUser = eventOfCurrentUser;
    }
    
    public void deleteEvent(){
        if(isEventOfCurrentUser()) {
            //rimuove le notifiche che puntano all'evento
            if(event.getNotifications() != null) {
                for(Notification n : event.getNotifications()) {
                    n.getInvitation().setNotification_id(null);
                    getIm().update(n.getInvitation());
                    getNm().remove(n);
                }
            }
            
            //rimuove gli inviti che puntano all'evento e l'evento stesso dal calendario di eventuali partecipanti
            if(event.getNotifications() != null) {
                for(Invitation i : event.getInvitations()) {
                    i.getUserInvited().getCalendar().getEventCollection().remove(event);
                    getIm().remove(i);
                }
            }
            
            //rimuove l'evento dal calendario e dal database
            event.getCreator().getCalendar().getEventCollection().remove(event);
            getEm().remove(event);
            
            //ATT: da trovare un modo per rimuovere l'evento dalla vista degli invitati, altrimenti rimane su fin quando non riparte il progetto!!!
            //rimuove l'evento dalla vista
            ScheduleEvent eventToRemove = eventModel.getEvent(scheduleEvent.getId());
            eventModel.deleteEvent(eventToRemove);
        } else {
            //se l'evento non e' dell'utente che vuole eliminarlo fare in modo che venga rimosso solo dal suo calendario, e l'invito rifiutato
            MeteoCalScheduleEvent eventToRemove = (MeteoCalScheduleEvent)eventModel.getEvent(scheduleEvent.getId());
            eventModel.deleteEvent(eventToRemove);
            //devo mettere status a false
            Invitation inv = im.getInvitationByEventAndUser(event, um.getLoggedUser());
            
            inv.setStatus(false);
            im.update(inv);
            
            Calendar cal= um.getLoggedUser().getCalendar();
            Collection<Event> eventCollection = cal.getEventCollection();
            Event toRemoveFromList=null;
            for(Event i: eventCollection){
                if(i.getId()==eventToRemove.getIdEvent()){
                    toRemoveFromList=i;
                }
            }
            eventCollection.remove(toRemoveFromList);
            cal.setEventCollection(eventCollection);
            um.getLoggedUser().setCalendar(cal);
            cl.editCalendar(cal);
            
        }
    }
    
    
    public String getMessage() {
        return message;
    }
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    //add messages to button save in view
    public void saveMessage() {
        FacesContext context = FacesContext.getCurrentInstance();
        Utils u = new Utils();
        /*if(badWeatherLevel!=0 && outdoor==false){
        context.addMessage(null, new FacesMessage("ERROR",  "Your message: you are trying to set an event with bebWeatherLevel, but without checking outdoor " ) );
        context.addMessage(null, new FacesMessage("Additional Message", "try to set the Event again"));
        setCreateEventOk(false);
        }else */if(outdoor == true && badWeatherLevel == 0) {
            setBadWeatherLevel(3);
            context.addMessage(null, new FacesMessage("Success",  "Weather Alert Level has been defaulted to Rain, since it is an outdoor event"));
            setCreateEventOk(true);
        } else if(title==null) {
            context.addMessage(null, new FacesMessage("ERROR",  "Please, set the event again"));
            context.addMessage(null, new FacesMessage("Additional Information", "Specify a title for your event"));
            setCreateEventOk(false);
        } else if(Utils.timeConsistent(ub.getUser().getCalendar(),getStartDate(),getEndDate()) == false) {
            context.addMessage(null, new FacesMessage("ERROR",  "Please, set the event again"));
            context.addMessage(null, new FacesMessage("Additional Information", "There are some time problem with your event, verify that you're not busy at that time or that it is consistent"));
            setCreateEventOk(false);
        } else if(validInviteesList(invitees, ub.getUser()) == false) {
            context.addMessage(null, new FacesMessage("ERROR",  "Please, set the event again"));
            context.addMessage(null, new FacesMessage("Additional Information", "Verify that your invitee's list is composed by valid email addresses separated by a comma (,)"));
            setCreateEventOk(false);
        } else if((locationName==null)&&(badWeatherLevel!=0)){
            context.addMessage(null, new FacesMessage("ERROR",  "Please, set the event again"));
            context.addMessage(null, new FacesMessage("Additional Information", "you are trying to set the bad weather level without specifying the location"));
            setCreateEventOk(false);
        }else{
            context.addMessage(null, new FacesMessage("Success",  "The event can be created"));
            setCreateEventOk(true);
        }
        
        
    }
    
    /**
     * @return the createEventOk
     */
    public boolean isCreateEventOk() {
        return createEventOk;
    }
    
    /**
     * @param createEventOk the createEventOk to set
     */
    public void setCreateEventOk(boolean createEventOk) {
        this.createEventOk = createEventOk;
    }
    
    public void modifyEvent(Event referingEvent, Date newStartDate, Date newEndDate) {
        referingEvent.setStartDate(newStartDate);
        referingEvent.setEndDate(newEndDate);
        
        getEm().edit(referingEvent);
        
        eventModel.clear();
        List<Event> eventCollection = (List<Event>) ub.getUser().getCalendar().getEventCollection();
        
        for (Event i : eventCollection) {
            eventModel.addEvent(new MeteoCalScheduleEvent(i.getId(), i.getTitle(), i.getStartDate(), i.getEndDate()));
        }
        
        
    }
    
    private boolean validInviteesList(String invitees, User user) {
        if(invitees == null) {
            return true;
        } else if(!Utils.validInviteesString(invitees, user.getEmail())) {
            return false;
        } else {
            String[] splittedString = invitees.split(",( )*");
            for(String s : splittedString) {
                User u = um.find(s);
                if(u == null) {
                    return false;
                }
            }
            return true;
        }
    }
}